<?php
class cmspad_database_connection_mysql implements DatabaseConnection{
	
	private $connection = NULL;

	private $batchSQL = array();
	
	/**
	 * 
	 * @see DatabaseConnection::addBatch()
	 */
	public function addBatch($sql){
		$this->batchSQL[] = trim($sql, ';');
	}
	
	/**
	 * 
	 * @see DatabaseConnection::connect()
	 */
	public function connect($host, $name, $username, $password, $port = 3306, $encoding = 'utf8'){
		$this->connection = mysqli_init();
		if(!@mysqli_real_connect($this->connection, $host, $username, $password, $name, $port)){
			$this->__checkConnectionError();
		}else{
			if(!@mysqli_query($this->connection, 'SET NAMES \''.$encoding.'\'')){
				$this->__checkDatabaseError();
			}
		}
		return $this->connection;
	}
	
	/**
	 * 
	 * @see DatabaseConnection::disconnect()
	 */
	public function disconnect(){
		if($this->connection){
			if(!mysqli_close($this->connection)){
				$this->__checkDatabaseError();
			}
			$this->connection = NULL;
		}
	}
	
	/**
	 * 
	 * @see DatabaseConnection::executeBatch()
	 */
	public function executeBatch(){
		if(!empty($this->batchSQL)){
			$sqls = implode(';', $this->batchSQL);
			if(mysqli_multi_query($this->connection, $sqls)){
				$results = array();
				for($result = mysqli_store_result($this->connection);mysqli_next_result($this->connection);$result = mysqli_store_result($this->connection)) {
					$this->__checkDatabaseError();
					$rows = array();
					while (($row = mysqli_fetch_array($result))) {
						$this->__checkDatabaseError();
						$rows[] = $row;
					}
					mysqli_free_result($result);
					$this->__checkDatabaseError();
					$results[] = $rows;
				}
				$this->batchSQL = array();
				return $results;
			}else{
				$this->batchSQL = array();
				$this->__checkDatabaseError();
				return false;
			}
		}else{
			$this->__checkDatabaseError();
			return false;
		}
	}
	
	/**
	 * 
	 * @see DatabaseConnection::isConnected()
	 */
	public function isConnected(){
		return $this->connection !== NULL;
	}
	
	/**
	 * 
	 * @see DatabaseConnection::prepare()
	 */
	public function prepare($sql, $length = 0, $offset = 0){
		$statement = mysqli_stmt_init($this->connection) or $this->__checkDatabaseError();
		return new cmspad_database_statement_mysql($this->connection, $statement, $this->__parseLimitSyntax($sql, $length, $offset));
	}
	
	/**
	 * 
	 * @see DatabaseConnection::query()
	 */
	public function query($sql,$length = 0,$offset = 0){
		$sql = $this->__parseLimitSyntax($sql, $length, $offset);
		if(($query_id = mysqli_query($this->connection, $sql))){
			$result = array();
			while (($row = mysqli_fetch_assoc($query_id))){
				$this->__checkDatabaseError();
				$result[] = $row;
			}
			mysqli_free_result($query_id);
			$this->__checkDatabaseError();
			return $result;
		}else{
			$this->__checkDatabaseError();
			return false;
		}
	}
	
	/**
	 * 
	 * @see DatabaseConnection::update()
	 */
	public function update($sql,$length = 0,$offset = 0){
		$sql = $this->__parseLimitSyntax($sql, $length, $offset);
		if(mysqli_query($this->connection, $sql)){
			if(strpos($sql, 'INSERT') === 0 || strpos($sql, 'insert') === 0){
				$i = mysqli_insert_id($this->connection);
			}else{
				$i = mysqli_affected_rows($this->connection);
			}
		}else{
			$i = false;
		}
		$this->__checkDatabaseError();
		return $i;
	}
	
	/**
	 * 处理LIMIT的语法参数
	 *
	 * @param string $sql
	 * @param int $length
	 * @param int $offset
	 * @return string 返回生成LIMIT语法后的SQL语句
	 */
	private function __parseLimitSyntax($sql, $length = 0, $offset = 0){
		if($length > 0 || $offset > 0){
			// if $total is set to 0 we do not want to limit the number of rows
			if ($length == 0){
				// MySQL 4.1+ no longer supports -1 in limit queries
			$length = '18446744073709551615';
			}

			$sql .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $length : $length);
		}
		return $sql;
	}
	
	private function __checkConnectionError(){
		if(mysqli_connect_errno()){
			throw new DatabaseException(mysqli_connect_errno(), mysqli_connect_error());
		}
	}
	
	private function __checkDatabaseError(){
		if(mysqli_errno($this->connection)){
			throw new DatabaseException(mysqli_errno($this->connection), mysqli_error($this->connection));
		}
	}
}

class cmspad_database_statement_mysql implements DatabaseStatement {

	private $connection;

	private $statement;

	private $parameterValues;

	private $parameterTypes;

	private $queryType;

	public function __construct($connection_id, $statement_id, $sql){
		$this->connection = $connection_id;
		$this->statement = $statement_id;
		$this->parameterValues = array();
		$this->parameterTypes = array();
		if(mysqli_stmt_prepare($statement_id, $sql)){
			switch(strtoupper(substr($sql, 0, 5))){
				case 'SELEC':
				case 'SHOW ':
					$this->queryType = 1;
					break;
				case 'INSER':
					$this->queryType = 2;
					break;
				case 'UPDAT':
				case 'DELET':
				case 'TRUNC':
					$this->queryType = 3;
					break;
			}
		}else{
			$this->__checkStatementError();
		}
	}

	/**
	 * @see DatabaseStatement::setString()
	 */
	public function setString($index, $value){
		$this->parameterValues[$index] = strval($value);
		$this->parameterTypes[$index] = 's';
	}

	/**
	 * @see DatabaseStatement::setDouble()
	 */
	public function setDouble($index, $value){
		$this->parameterValues[$index] = doubleval($value);
		$this->parameterTypes[$index] = 'd';
	}

	/**
	 * @see DatabaseStatement::setInteger()
	 */
	public function setInteger($index, $value){
		$this->parameterValues[$index] = intval($value);
		$this->parameterTypes[$index] = 'i';
	}

	/**
	 * @see DatabaseStatement::setBinary()
	 */
	public function setBinary($index, $value){
		$this->parameterValues[$index] = $value;
		$this->parameterTypes[$index] = 'b';
	}

	/**
	 * @see DatabaseStatement::execute()
	 */
	public function execute(){
		if(!empty($this->parameterTypes)){ //FIX(5.3.0): Warning: mysqli_stmt_bind_param(): Invalid type or no types specified in /kernel/inc/database/drivers/mysql.php(253) : eval()'d code on line 1
			$types = '';
			foreach($this->parameterTypes as $type){
				$types .= $type;
			}
	
			$cmdline = 'mysqli_stmt_bind_param($this->statement,\''.$types.'\'';
			for($i = 0, $l = count($this->parameterValues);$i < $l;$i++){
				$cmdline .= ',$this->parameterValues['.$i.']';
			}
			$cmdline .= ');';
	
			eval($cmdline);
			$this->__checkStatementError();
		}
		mysqli_stmt_execute($this->statement);
		$this->__checkStatementError();
			
		$values = array();

		$result_metadata = mysqli_stmt_result_metadata($this->statement);
		if($result_metadata){ // 只有查询时可以返回结果
			$fields = mysqli_fetch_fields($result_metadata);
			$this->__checkStatementError();
	
			$fieldl = count($fields);
			$cmdline = 'mysqli_stmt_bind_result($this->statement';
			for($i = 0;$i < $fieldl;$i++){
				$values[$i] = NULL;
				$cmdline .= ',$values['.$i.']';
			}
			$cmdline .= ');';
	
			eval($cmdline);
			$this->__checkStatementError();
		}
		
		$results = NULL;
		switch($this->queryType){
			case 1: // SELECT
				$results = array();
				// Query
				while (mysqli_stmt_fetch($this->statement)) {
					$result = array();
					for($i = 0;$i < $fieldl;$i++){
						$value = $values[$i];
						$field = $fields[$i];
						$result[$i] = $value;
						$result[$field->name] = $value;
					}
					$results[] = $result;
				}
				break;
			case 2: // INSERT
				$results = mysqli_insert_id($this->connection);
				break;
			case 3: // UPDATE,DELETE,TRUNCATE
				$results = mysqli_stmt_affected_rows($this->statement);
				break;
		}
		$this->__checkStatementError();

		mysqli_stmt_free_result($this->statement);
		$this->__checkStatementError();

		return $results;
	}

	/**
	 * @see DatabaseStatement::reset()
	 */
	public function reset(){
		mysqli_stmt_reset($this->statement);
		$this->parameterTypes = array();
		$this->parameterValues = array();
		$this->__checkStatementError();
	}

	/**
	 * @see DatabaseStatement::close()
	 */
	public function close(){
		if($this->statement){
			if(!mysqli_stmt_close($this->statement)){
				$this->__checkStatementError();
			}
			$this->statement = NULL;
		}
	}
	
	private function __checkStatementError(){
		if(mysqli_stmt_errno($this->statement)){
			throw new DatabaseException(mysqli_stmt_errno($this->statement), mysqli_stmt_error($this->statement));
		}
	}
}
?>
